-- Matías Blanco
-- Audi A3 - Examen final - Tipos Protegidos en ADA

with Ada.Text_Io ; use Ada.Text_Io;
with Ada.Integer_Text_Io; use Ada.Integer_Text_Io;
with Ada.Numerics.Float_Random ; use Ada.Numerics.Float_Random;

procedure A3TProtAda is
   -- Costantes y variables
   MAXCIUDADANOS : constant integer := 15;
   MAXPUESTOS : constant integer := 3;
   MAXOBSTACULOS : constant integer := 10;

   G : Generator;

   -- Definimos el tipo enumerador
   type EstadoSalirCoche is (Ganar, Perder); -- Cuando te echan del coche puedes ganar o perder
   type EstadoPuesto is (Abierto, Cerrado); -- Estado en el que puede estar un puesto

   -- Definimos los tipos protegidos
   protected type Coche is
      procedure EntrarCoche(idCiudadano : integer);
      procedure SacarDelCoche;
      entry SalirDelCoche(estado : out EstadoSalirCoche);
      procedure QuitarObstaculo(esquivado : out boolean);
      function DarObstaculosRecogidos return Integer;
      private
         Conductor : integer := 0; -- Conductor actual del coche
         Obstaculos : integer := 0; -- Numero te obtaculos que se van poniendo
         Ocupado : boolean; -- Indica si el coche esta ocupado o no
   end Coche;

   protected type Puesto is
      procedure Abrir(idPuesto: integer; obstaculosIniciales : integer);
      procedure DarObstaculo(obstaculo : out boolean);
      private
         id : integer;
         estado : EstadoPuesto;
         Obstaculos : integer; -- Numero te obtaculos que tiene el puesto
   end Puesto;

   task type Ciudadano(idCiudadano : integer);

   task Reloj;

   -- Definimos los tipos protegidos que vamos a usar
   cocheA3 : Coche;

   type PCiudadano is access Ciudadano; -- Definitos el tipo puntero de Ciudadano
   -- Definimos el array de punteros de ciudanos
   ciudadanos: array(1..MAXCIUDADANOS) of PCiudadano;

   type PPuesto is access Puesto; -- Definitos el tipo puntero de Puesto
   -- Definimos el array de punteros de Puestos
   puestos: array(1..MAXPUESTOS) of PPuesto;

   protected body Coche is
      procedure EntrarCoche(idCiudadano : integer) is
      begin
         Conductor := idCiudadano;
         Ocupado := true;
      end EntrarCoche;
      
      procedure SacarDelCoche is
      begin
         -- Sacamos del coche al conductor actual
        Ocupado := false;
      end SacarDelCoche;

      entry SalirDelCoche(estado : out EstadoSalirCoche) when not ocupado is -- Hasta que no echen al conductor no puede salir
      begin
         if Obstaculos = MAXOBSTACULOS then -- Si ya estan todos los obtaculos esquivados hemos ganado
            estado := Ganar;
         else
            estado := Perder;
         end if;
      end SalirDelCoche;

      procedure QuitarObstaculo(esquivado : out boolean) is
         aleatorio : float;
      begin
         Obstaculos := Obstaculos + 1; -- Ya se ha procesado un obstaculo
         -- Decidimos aleatoriamente si lo esquivamos o no
         Reset(G);
         aleatorio := Random(G);
         if aleatorio > 0.5 then -- Se esquiva
            esquivado := true;
         else -- No se esquiva
            esquivado := false;
         end if;
      end QuitarObstaculo;

      function DarObstaculosRecogidos return Integer is
      begin
         return Obstaculos;
      end DarObstaculosRecogidos;
   end Coche;

   protected body Puesto is
      procedure Abrir(idPuesto:integer; obstaculosIniciales : integer) is
      begin
         -- Abrimos el puesto con un numero de obstaculos
         id := idPuesto;
         estado := Abierto;
         Obstaculos := obstaculosIniciales;
      end Abrir;

      procedure DarObstaculo(obstaculo : out boolean) is
         aleatorio : float;
      begin
         if estado = Cerrado then -- El Puesto esta cerrado ya no damos mas obstaculos
            obstaculo := false;
         else
            -- Le damos el obtaculo
            Reset(G);
            aleatorio := Random(G);
            delay Duration(aleatorio * 10.0); -- Tiempo de atender al ciudadano
            -- Le damos el obstaculo
            Obstaculos := Obstaculos - 1;
            obstaculo := true;

            -- Vemos si es el ultimo el que hemos dado para cerrar el Puesto
            if Obstaculos = 0 then
               estado := Cerrado;
               Put("&& El puesto ");Put(id,0);Put(" se ha cerrado porque ya no tiene mas obstaculos.");New_Line;
            end if;
         end if;
      end DarObstaculo;
   end Puesto;

   task body Ciudadano is
      id: integer;
      obstaculo: boolean;
      estado : EstadoSalirCoche;
      esquivado : boolean;
   begin
      -- Inicializamos los valores
      id := idCiudadano;
      Reset(G);
      -- Cogemos un obstaculo
      for i in 1..MAXPUESTOS loop
         delay Duration(Random(G) * 10.0); -- Tiempo en llegar al puesto
         puestos(i).DarObstaculo(obstaculo);
         exit when obstaculo; -- Cuando ya tengamos uno dejamos de pedir un obstaculo
      end loop;
         
      if obstaculo then -- Cuando tengamos un obstaculo lo ponemos
         Put("### El ciudadano ");Put(id,0);Put(" ha puesto un obstaculo.");New_Line;
         cocheA3.QuitarObstaculo(esquivado);
         if not esquivado then
            Put("--> El ciudadano ");Put(id,0);Put(" ha parado el Audi A3 y se monta en él.");New_Line;
            cocheA3.SacarDelCoche; -- Sacamos al conductor
            cocheA3.EntrarCoche(id); -- Entramos en el coche
            cocheA3.SalirDelCoche(estado); -- Saldremos del coche cuando nos echen
            -- Vemos si hemos ganado
            if estado = Ganar then
               Put("¡¡¡¡ El ciudadano ");Put(id,0);Put(" ha ganado el Audi A3 !!!!");New_Line;
            else
               Put("<-- El ciudadano ");Put(id,0);Put(" se retira porque lo han parado con un obstaculo.");New_Line;
            end if;
         else
            Put("<%% El ciudadano ");Put(id,0);Put(" se retira porque el conductor del coche a esquivado su obstaculo.");New_Line;
         end if;
      else
         Put("<// El ciudadano ");Put(id,0);Put(" se retira porque no tiene obstaculos para poner.");New_Line;
      end if;
   end Ciudadano;

   task body Reloj is
      obstaculos : integer;
   begin
      loop
         -- Lo que mirar los obstaculos que ha recogido el coche y si ha recogido todos saca al último conductos
         obstaculos := cocheA3.DarObstaculosRecogidos;
         if obstaculos = MAXOBSTACULOS then
            cocheA3.SacarDelCoche; -- Sacamos al conductor
         end if;
         exit when obstaculos = MAXOBSTACULOS; -- Finalizamos el reloj
         delay 1.0;
      end loop;
      Put_Line("---- FIN DEL JUEGO ----");
   end Reloj;
begin
   Put("---- EMPIEZA EL JUEGO ----");New_Line;New_Line;
   -- Creamos los puestos y os abrimos
   for i in 1..MAXPUESTOS loop
      puestos(i) := new Puesto;
   end loop;
   puestos(1).Abrir(1,3);
   puestos(2).Abrir(2,3);
   puestos(3).Abrir(3,4);

   -- Empiezan a salir los ciudadanos
   for i in 1..MAXCIUDADANOS loop
      ciudadanos(i) := new Ciudadano(i);
   end loop;
end A3TProtAda;
